package minov.parser;

import java.util.Deque;
import java.util.ArrayList;

/**
 * {@class MultipleValueArgument}
 * @brief  hello.
 * Generic class for arguments with parameters.
 * 
 * 
 */
public abstract class MultipleValueArgument<T> extends
      ArgumentObject {

   /**
    * Constructor
    * 
    * @param [in] shortName
    *           One letter abbreviation of the argument.
    * @param [in] fullName
    *           Long name of the argument.
    * @param [in] defaultValues
    *           The default values to be used.
    */
   @SuppressWarnings("unchecked")
   public MultipleValueArgument(String shortName, String fullName,
         T... defaultValues) {
      super();
      super.addArgumentAliases(shortName, fullName);

      // set default values
      for (T defaultValue : defaultValues) {
         _defaultValues.add(defaultValue);
      }

   }

   /**
    * Result entered values.
    * If
    */
   private ArrayList<T> _values = new ArrayList<T>();

   private ArrayList<T> _defaultValues = new ArrayList<T>();

   /**
    * Method for processing the parameters. Used by the parser.
    * 
    * @param [in] arguments
    *           Deque with the arguments
    */
   @Override
   public void parseArguments(Deque<ParsedArgumentData> arguments) {
      if (arguments.isEmpty())
         return;

      int numberOfArguments = 0;
      int maxNumberOfArguments = getMaximalCount();
      boolean getNextArgument =
            (arguments.isEmpty() == false)
                  && (numberOfArguments < maxNumberOfArguments);

      while (getNextArgument) {

         ParsedArgumentData inputArgument = arguments.poll();
         if (inputArgument.valueIsSet) {

            // try to convert the string parameter to Double
            try {
               String stringValue = inputArgument.value;
               T doubleValue = parseOneArgument(stringValue);
               this._values.add(doubleValue);
               numberOfArguments++;
               getNextArgument =
                     (arguments.isEmpty() == false)
                           && (numberOfArguments < maxNumberOfArguments);
            }
            // when conversion fails, push value back to queue
            catch (Exception e) {
               arguments.push(inputArgument);
               getNextArgument = false;
            }

         } else {
            arguments.push(inputArgument);
            getNextArgument = false;
         }
      }

      this.setEnteredValues(this._values.size());

      return;
   }

   /**
    * Function for processing one argument. Called by the parser. Has to be implemented in each Argument class with parameters.
    * @param [in] text value to be processed
    * @return processed value
    */
   public abstract T parseOneArgument(String text) throws Exception;

   /*
    * format strings for validation result
    */
   private static final String UNDEFINED_DEFAULTS_FORMAT_STRING =
         "Argument %s has not defined default values.";

   private static final String BAD_NUMBER_FORMAT_STRING =
         "Argument %s is not within specified boundaries.";

   
   private void setDefaultsAsResultValues() {
      for (T defaultValue : _defaultValues) {
         _values.add(defaultValue);
      }
   }

   
   /**
    * Function for validating one argument. Called by the parser. Has to be implemented in each Argument class with parameters.
    * @param [in] value value to be validated
    * @return ValidationResult object with the result
    */
   public abstract ValidationResult validateValue(T value);

   /**
    * Method for validation of the parsed arguments.
    * 
    * @return ValidationResult object with the result
    */
   @Override
   public final ValidationResult validateArgument() {

      // iterate through the parameter values
      // if the argument wasn't used, use default values
      if ( isEntered() == false) {
         if (_defaultValues.isEmpty() && this.getMinimalCount()>0) {
            return ValidationResult.createInvalidResponse(String.format(
                  UNDEFINED_DEFAULTS_FORMAT_STRING, getDefaultName()));
         }
         setDefaultsAsResultValues();
      }else
      {
         Boolean allValid = true;
         for (T currentValue : _values) {
            // check whether the value is a number and is within boundaries
            ValidationResult oneValueResult = validateValue(currentValue);
            if (oneValueResult.isValid() == false) {
               allValid = false;
            }
         }
         if (allValid == false) {
            return ValidationResult.createInvalidResponse(String.format(
                  BAD_NUMBER_FORMAT_STRING, getDefaultName()));
         }
         
      }
      
      return super.validateArgument();

   }

   /**
    * Function for getting the first parameter value
    * @return value of the parameter
    */
   public T getValue() {
      if (_values.isEmpty()) {
         if (_defaultValues.isEmpty()) {
            return null;
         } else {
            return _defaultValues.get(0);
         }
      } else {
         return _values.get(0);
      }
   }

   abstract T[] getGenericArray();

   private T[] GENERIC_ARRAY_TYPE = getGenericArray();

   /**
    * Function for getting all the argument's parameters
    * @return array with the parameter values
    */
   public T[] getAllValues() {
      return _values.toArray(GENERIC_ARRAY_TYPE);
   }

   /**
    * Resets the ArgumentObject for further use.
    */
   @Override
   public void reset() {
      super.reset();
      _values.clear();
   }

}
